/****************************************************************************/
/* This file is part of FreeFem++.                                          */
/*                                                                          */
/* FreeFem++ is free software: you can redistribute it and/or modify        */
/* it under the terms of the GNU Lesser General Public License as           */
/* published by the Free Software Foundation, either version 3 of           */
/* the License, or (at your option) any later version.                      */
/*                                                                          */
/* FreeFem++ is distributed in the hope that it will be useful,             */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/* GNU Lesser General Public License for more details.                      */
/*                                                                          */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with FreeFem++. If not, see <http://www.gnu.org/licenses/>.        */
/****************************************************************************/
/* SUMMARY : ... */
/* LICENSE : LGPLv3 */
/* ORG     : LJLL Universite Pierre et Marie Curie, Paris, FRANCE */
/* AUTHORS : Pascal Frey */
/* E-MAIL  : pascal.frey@sorbonne-universite.fr
 */

#include "medit.h"
#include "extern.h"
#include "sproto.h"

#ifndef  ON
#define  ON 1
#define  OFF 0
#endif

GLuint lasttime;
GLboolean tracking = GL_FALSE, ctracking = GL_FALSE;
GLboolean picking = GL_FALSE;
int cbutton = 0;
int startx, starty, curx, cury;
int rxi, ryi, rx1, rx2, ry1, ry2;

#define MinWH 0.1
#define MaxWH 0.9

#ifndef GLUT_BUTTON_3
#define GLUT_BUTTON_3 2
#define GLUT_BUTTON_4 3
#endif

/* project x,y, onto a hemi-sphere */
static void point2Vect (int x, int y, int w, int h, float *v) {
	double d, a, areax, areay;

	areax = (w - startx) / w;
	areay = (h - starty) / h;
	if (areax > MinWH && areax < MaxWH && areay > MinWH && areay < MaxWH) {
		v[0] = (2.0 * x - w) / w;
		v[1] = (h - 2.0 * y) / h;
		v[2] = 1.0f;
	} else {
		v[0] = 2.0f * (x - startx) / w;
		v[1] = -2.0f * (y - starty) / h;
		v[2] = 1.0f;
	}

	d = v[0] * v[0] + v[1] * v[1];
	if (d == 0.0f) return;

	d = sqrt(d);

	v[2] = cos(M_PI_2 * ((d < 1.0) ? d : 1.0));
	d = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
	a = 1.0f / sqrt(d);
	v[0] *= a;
	v[1] *= a;
	v[2] *= a;
}

void ortho2D (pScene sc, ubyte mode) {
	if (mode == ON) {
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0, sc->par.xs, 0., sc->par.ys);
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
		glMatrixMode(GL_MODELVIEW);
	} else if (mode == OFF) {
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
		glEnable(GL_DEPTH_TEST);
		glLineWidth(1.);
	}
}

static void drawRubberBand (int xa, int ya, int xb, int yb) {
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glLineWidth(2.0);
	glColor3f(0.5, 0.5, 0.5);
	glRecti(xa, ya, xb, yb);
}

static void rubberMotion (int x, int y) {
	pScene sc;
	pPersp p;

	sc = cv.scene[currentScene()];
	p = sc->persp;

	glEnable(GL_COLOR_LOGIC_OP);
	glLogicOp(GL_XOR);

	/* draw frame */
	drawRubberBand(rxi, ryi, rx1, ry1);
	rx2 = x;
	ry2 = sc->par.ys - y;
	drawRubberBand(rxi, ryi, rx2, ry2);
	glFlush();

	glLogicOp(GL_COPY);
	glDisable(GL_COLOR_LOGIC_OP);

	/* keep old coords */
	rx1 = rx2;
	ry1 = ry2;
}

void zoomMotion (int x, int y) {
	pScene sc;
	pPersp p;
	int dy;

	sc = cv.scene[currentScene()];
	p = sc->persp;

	dy = starty - y;
	if (dy > 0)
		if (p->fovy < 1.0e-02)
			return;
		else
			p->fovy = max(0.95 * p->fovy, 1e-05);

	else if (p->fovy > 160.0) return;
	else
		p->fovy = min(1.1 * p->fovy, 179.0);

	farclip(1);
	starty = y;
	glutPostRedisplay();
}

void mouse (int button, int state, int x, int y) {
	pScene sc;
	pTransform tr;
	pPersp p;
	int keyact, idw = currentScene();
	static int olds = -1;

	picking = GL_FALSE;

	/* default */
	if (ddebug) printf("control mouse %d\n", state);

	sc = cv.scene[idw];
	p = sc->persp;
	if (sc->cube->active & C_EDIT)
		tr = sc->cube->cubetr;
	else if (sc->clip->active & C_EDIT)
		tr = sc->clip->cliptr;
	else
		tr = sc->view;

	tr->mstate = state;
	tr->mbutton = button;

	/* check if ctrl-shift-alt pressed */
	keyact = glutGetModifiers();

	if (state == GLUT_DOWN) {
		tracking = GL_TRUE;
		lasttime = glutGet(GLUT_ELAPSED_TIME);

		if (button == GLUT_LEFT_BUTTON) {
			if (keyact == GLUT_ACTIVE_SHIFT) {
				/* entity designation */
				picking = GL_TRUE;
				if (sc->picklist) glDeleteLists(sc->picklist, 1);

				sc->picklist = pickingScene(sc, x, y, 0);
				return;
			} else if (keyact == GLUT_ACTIVE_ALT) {
				/* zoom */
				starty = y;
				glutMotionFunc(zoomMotion);
				return;
			} else if (keyact == GLUT_ACTIVE_CTRL) {
				/* rubberband selection */
				glutSetCursor(GLUT_CURSOR_CROSSHAIR);
				p->rubix = p->rubfx = x;
				p->rubiy = p->rubfy = sc->par.ys - y;
				rxi = rx1 = x;
				ryi = ry1 = sc->par.ys - y;
				p->rubber = 1;
				glDrawBuffer(GL_BACK_LEFT);
				ortho2D(sc, ON);
				glutMotionFunc(rubberMotion);
				return;
			}
		} else if (button == GLUT_MIDDLE_BUTTON && keyact & GLUT_ACTIVE_SHIFT) {
			picking = GL_TRUE;
			if (sc->picklist) glDeleteLists(sc->picklist, 1);

			sc->picklist = pickingScene(sc, x, y, LPoint);
			return;
		}

		/* transformation */
		startx = x;
		starty = y;
		point2Vect(x, y, sc->par.xs, sc->par.ys, tr->pos);
		glutSetCursor(GLUT_CURSOR_INFO);
	} else if (state == GLUT_UP) {
		if (button == GLUT_LEFT_BUTTON) {
			if (keyact == GLUT_ACTIVE_CTRL) {
				/* rubberband selection */
				p->rubfx = x;
				p->rubfy = sc->par.ys - y;
				p->rubber = 2;
				glDrawBuffer(GL_BACK_LEFT);
				ortho2D(sc, OFF);
				glutMotionFunc(motion);
				return;
			} else if (keyact == GLUT_ACTIVE_ALT) {
				glutMotionFunc(motion);
				return;
			} else if (picking == GL_TRUE) {
				picking = GL_FALSE;
				reshapeScene(sc->par.xs, sc->par.ys);
				glutPostRedisplay();
			}
		}

		glutMotionFunc(motion);
		if (sc->clip->active & C_EDIT)
			sc->clip->active |= C_REDO;

		/* transformation */
		glutSetCursor(GLUT_CURSOR_INHERIT);
		tracking = GL_FALSE;
		if (glutGet(GLUT_ELAPSED_TIME) >= lasttime) {
			if (tr->manim == GL_TRUE) glutIdleFunc(glutIdle);
			else tr->angle = 0.0;

			/*if ( abs(startx-x) + abs(starty-y) > 0 )*/
			glutPostRedisplay();
		} else if (tr->manim == GL_TRUE && olds == idw) {
			glutIdleFunc(NULL);
		}
	}

	olds = idw;
}

void motion (int x, int y) {
	pScene sc;
	pTransform tr;
	pPersp p;
	GLuint gtime;
	double deltax, deltay;
	float coeff, pos[3];
	int idw = currentScene();

	/* default */
	if (picking) return;

	if (ddebug) fprintf(stdout, "motion\n");

	if (tracking == GL_FALSE) return;

	sc = cv.scene[idw];
	p = sc->persp;
	if (p->rubber == 1) return;

	/* what is transformed ? */
	if (sc->cube->active & C_EDIT)
		tr = sc->cube->cubetr;
	else if (sc->clip->active & C_EDIT)
		tr = sc->clip->cliptr;
	else
		tr = sc->view;

	if (tr->mstate != GLUT_DOWN) return;

	if (picking) tr->angle = 0.0f;

	gtime = glutGet(GLUT_ELAPSED_TIME);
	if ((animate || sc->type & S_FOLLOW) && gtime < lasttime + 40) return;

	if (tr->mbutton == GLUT_LEFT_BUTTON) {
		/* calculate axis of rotation: cross product */
		point2Vect(x, y, sc->par.xs, sc->par.ys, pos);
		tr->axis[0] = tr->pos[1] * pos[2] - tr->pos[2] * pos[1];
		tr->axis[1] = tr->pos[2] * pos[0] - tr->pos[0] * pos[2];
		tr->axis[2] = tr->pos[0] * pos[1] - tr->pos[1] * pos[0];

		/* calculate angle to rotate by */
		if (animate && saveimg) {
			tr->angle = 2.0f;
		} else {
			float dx, dy, dz;
			
			dx = pos[0] - tr->pos[0];
			dy = pos[1] - tr->pos[1];
			dz = pos[2] - tr->pos[2];
			tr->angle = 180.0 * sqrt(dx * dx + dy * dy + dz * dz);
		}

		/* reset for next time */
		tr->pos[0] = pos[0];
		tr->pos[1] = pos[1];
		tr->pos[2] = pos[2];
		lasttime = gtime;

		if (sc->cube->active & C_ON && sc->cube->active & C_EDIT)
			sc->cube->active |= C_UPDATE;
		else if (sc->clip->active & C_ON &&
		         (sc->clip->active & C_EDIT || sc->clip->active & C_FREEZE))
			sc->clip->active |= C_UPDATE;

		glutPostRedisplay();
	} else if (tr->mbutton == GLUT_MIDDLE_BUTTON) {
		coeff = tr->manim == GL_TRUE ? 0.2 : 2.0;
		deltax = coeff * (x - startx) / (float)sc->par.xs;
		deltay = coeff * (starty - y) / (float)sc->par.ys;

		if (deltax != 0.0)
			tr->panx += -deltax *p->depth*tan (p->fovy / 360. *M_PI);

		if (deltay != 0.0)
			tr->pany += -deltay *p->depth*tan (p->fovy / 360. *M_PI);

		tr->angle = 0.0;
		startx = x;
		starty = y;

		lasttime = gtime;
		if (sc->cube->active & C_ON && sc->cube->active & C_EDIT)
			sc->cube->active |= C_UPDATE;
		else if (sc->clip->active & C_ON &&
		         (sc->clip->active & C_EDIT || sc->clip->active & C_FREEZE))
			sc->clip->active |= C_UPDATE;

		glutPostRedisplay();
	}
}

void mouseCamera (int button, int state, int x, int y) {
	/* default */
	if (ddebug) printf("control mouse camera %d button %d\n", state, button);

	cbutton = button;
	if (state == GLUT_DOWN) {
		ctracking = GL_TRUE;
		startx = x;
		starty = y;
		curx = x;
		cury = y;
	} else {
		startx = x;
		starty = y;
		ctracking = GL_FALSE;
	}
}

void motionCamera (int x, int y) {
	pScene sc;
	pCamera c;
	double dazim, delev, azim, elev;
	float cfelev, cfazim;

	/* keep current pos */
	curx = x;
	cury = y;

	if (animate) return;

	sc = cv.scene[currentScene()];
	c = sc->camera;
	azim = Azimuth(c);
	elev = Elevation(c);

	switch (cbutton) {
	case GLUT_LEFT_BUTTON:
		cfelev = 50.0;
		cfazim = 50.0;
		delev = cfelev * (y - starty) / (float)sc->par.ys;
		dazim = cfazim * (x - startx) / (float)sc->par.xs;
		startx = x;
		starty = y;
		elev += delev;
		azim -= dazim;
		break;
	case GLUT_MIDDLE_BUTTON:
		break;
	case GLUT_BUTTON_3:
		puts("button3");
		break;
	case GLUT_BUTTON_4:
		puts("button4");
		break;
	}

	updateCamera(sc, c, azim, elev);
	reshapeScene(sc->par.xs, sc->par.ys);
	glutPostRedisplay();
}

void animateCamera () {
	pScene sc;
	pCamera c;
	double dazim, delev, azim, elev;
	float cfelev, cfazim;

	if (!animate || !ctracking) return;

	sc = cv.scene[currentScene()];
	c = sc->camera;
	azim = Azimuth(c);
	elev = Elevation(c);

	switch (cbutton) {
	case GLUT_LEFT_BUTTON:
		cfelev = 3.0;
		cfazim = 3.0;
		delev = 2.0 * (cury - starty) / (float)sc->par.ys;
		dazim = 2.0 * (curx - startx) / (float)sc->par.xs;
		if (delev >= 0.0) delev *= delev;
		else delev = -delev * delev;

		if (dazim >= 0.0) dazim *= dazim;
		else dazim = -dazim * dazim;

		elev += cfelev * delev;
		azim -= cfazim * dazim;
		break;

	case GLUT_MIDDLE_BUTTON:
		break;
	case GLUT_BUTTON_3:
		puts("button3");
		break;
	case GLUT_BUTTON_4:
		puts("button4");
		break;
	}

	updateCamera(sc, c, azim, elev);
	reshapeScene(sc->par.xs, sc->par.ys);
	glutPostRedisplay();
}
